/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    DoubleVector.java
 *    Copyright (C) 2002-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core.matrix;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * A vector specialized on doubles.
 * 
 * @author Yong Wang
 * @version $Revision$
 */
public class DoubleVector implements Cloneable {

    double[] V; // array for internal storage of elements.

    private int sizeOfVector; // size of the vector

    /*
     * ------------------------ Constructors ------------------------
     */

    /**
     * Constructs a null vector.
     */
    public DoubleVector() {
        this(0);
    }

    /**
     * Constructs an n-vector of zeros.
     * 
     * @param n length.
     */
    public DoubleVector(int n) {
        V = new double[n];
        setSize(n);
    }

    /**
     * Constructs a constant n-vector.
     * 
     * @param n length.
     * @param s the scalar value used to fill the vector
     */
    public DoubleVector(int n, double s) {
        this(n);
        set(s);
    }

    /**
     * Constructs a vector directly from a double array
     * 
     * @param v the array
     */
    public DoubleVector(double v[]) {
        if (v == null) {
            V = new double[0];
            setSize(0);
        } else {
            V = v;
            setSize(v.length);
        }
    }

    /*
     * ------------------------ Public Methods ------------------------
     */

    /**
     * Set a single element.
     * 
     * @param i Index.
     * @param s a[i].
     */
    public void set(int i, double s) {

        V[i] = s;
    }

    /**
     * Set all elements to a value
     * 
     * @param s the value
     */
    public void set(double s) {
        set(0, size() - 1, s);
    }

    /**
     * Set some elements to a value
     * 
     * @param i0 the index of the first element
     * @param i1 the index of the second element
     * @param s  the value
     */
    public void set(int i0, int i1, double s) {

        for (int i = i0; i <= i1; i++) {
            V[i] = s;
        }
    }

    /**
     * Set some elements using a 2-D array
     * 
     * @param i0 the index of the first element
     * @param i1 the index of the second element
     * @param j0 the index of the starting element in the 2-D array
     * @param v  the values
     */
    public void set(int i0, int i1, double[] v, int j0) {
        for (int i = i0; i <= i1; i++) {
            V[i] = v[j0 + i - i0];
        }
    }

    /**
     * Set the elements using a DoubleVector
     * 
     * @param v the DoubleVector
     */
    public void set(DoubleVector v) {
        set(0, v.size() - 1, v, 0);
    }

    /**
     * Set some elements using a DoubleVector.
     * 
     * @param i0 the index of the first element
     * @param i1 the index of the second element
     * @param v  the DoubleVector
     * @param j0 the index of the starting element in the DoubleVector
     */
    public void set(int i0, int i1, DoubleVector v, int j0) {
        for (int i = i0; i <= i1; i++) {
            V[i] = v.V[j0 + i - i0];
        }
    }

    /**
     * Access the internal one-dimensional array.
     * 
     * @return Pointer to the one-dimensional array of vector elements.
     */
    public double[] getArray() {
        return V;
    }

    void setArray(double[] a) {
        V = a;
    }

    /**
     * Returns a copy of the DoubleVector usng a double array.
     * 
     * @return the one-dimensional array.
     */
    public double[] getArrayCopy() {
        double v[] = new double[size()];

        for (int i = 0; i < size(); i++) {
            v[i] = V[i];
        }

        return v;
    }

    /** Sorts the array in place */
    public void sort() {
        Arrays.sort(V, 0, size());
    }

    /** Sorts the array in place with index returned */
    public IntVector sortWithIndex() {
        IntVector index = IntVector.seq(0, size() - 1);
        sortWithIndex(0, size() - 1, index);
        return index;
    }

    /**
     * Sorts the array in place with index changed
     * 
     * @param xi    first index
     * @param xj    last index
     * @param index array that stores all indices
     */
    public void sortWithIndex(int xi, int xj, IntVector index) {
        if (xi < xj) {
            double x;
            int xm = (xi + xj) / 2; // median index
            x = Math.min(V[xi], // median of three
                    Math.max(V[xm], V[xj]));
            int i = xi;
            int j = xj;
            while (i < j) {
                while (V[i] < x && i < xj) {
                    i++;
                }
                while (V[j] > x && j > xi) {
                    j--;
                }
                if (i <= j) {
                    swap(i, j);
                    index.swap(i, j);
                    i++;
                    j--;
                }
            }
            sortWithIndex(xi, j, index);
            sortWithIndex(i, xj, index);
        }
    }

    /**
     * Gets the size of the vector.
     * 
     * @return the size
     */
    public int size() {
        return sizeOfVector;
    }

    /**
     * Sets the size of the vector
     * 
     * @param m the size
     */
    public void setSize(int m) {
        if (m > capacity()) {
            throw new IllegalArgumentException("insufficient capacity");
        }
        sizeOfVector = m;
    }

    /**
     * Gets the capacity of the vector.
     * 
     * @return the capacity.
     */
    public int capacity() {
        if (V == null) {
            return 0;
        }
        return V.length;
    }

    /**
     * Sets the capacity of the vector
     * 
     * @param n the capacity.
     */
    public void setCapacity(int n) {
        if (n == capacity()) {
            return;
        }
        double[] oldV = V;
        int m = Math.min(n, size());
        V = new double[n];
        setSize(m);
        set(0, m - 1, oldV, 0);
    }

    /**
     * Gets a single element.
     * 
     * @param i Index.
     * @return the value of the i-th element
     */
    public double get(int i) {
        return V[i];
    }

    /**
     * Adds a value to an element
     * 
     * @param i the index of the element
     * @param s the value
     */
    public void setPlus(int i, double s) {
        V[i] += s;
    }

    /**
     * Multiplies a value to an element
     * 
     * @param i the index of the element
     * @param s the value
     */
    public void setTimes(int i, double s) {
        V[i] *= s;
    }

    /**
     * Adds an element into the vector
     * 
     * @param x the value of the new element
     */
    public void addElement(double x) {
        if (capacity() == 0) {
            setCapacity(10);
        }
        if (size() == capacity()) {
            setCapacity(2 * capacity());
        }
        V[size()] = x;
        setSize(size() + 1);
    }

    /**
     * Returns the squared vector
     */
    public DoubleVector square() {
        DoubleVector v = new DoubleVector(size());
        for (int i = 0; i < size(); i++) {
            v.V[i] = V[i] * V[i];
        }
        return v;
    }

    /**
     * Returns the square-root of all the elements in the vector
     */
    public DoubleVector sqrt() {
        DoubleVector v = new DoubleVector(size());
        for (int i = 0; i < size(); i++) {
            v.V[i] = Math.sqrt(V[i]);
        }
        return v;
    }

    /**
     * Makes a deep copy of the vector
     */
    public DoubleVector copy() {
        return (DoubleVector) clone();
    }

    /**
     * Clones the DoubleVector object.
     */
    @Override
    public Object clone() {
        int n = size();
        DoubleVector u = new DoubleVector(n);
        for (int i = 0; i < n; i++) {
            u.V[i] = V[i];
        }
        return u;
    }

    /**
     * Returns the inner product of two DoubleVectors
     * 
     * @param v the second DoubleVector
     * @return the product
     */
    public double innerProduct(DoubleVector v) {
        if (size() != v.size()) {
            throw new IllegalArgumentException("sizes unmatch");
        }
        double p = 0;
        for (int i = 0; i < size(); i++) {
            p += V[i] * v.V[i];
        }
        return p;
    }

    /**
     * Returns the signs of all elements in terms of -1, 0 and +1.
     */
    public DoubleVector sign() {
        DoubleVector s = new DoubleVector(size());
        for (int i = 0; i < size(); i++) {
            if (V[i] > 0) {
                s.V[i] = 1;
            } else if (V[i] < 0) {
                s.V[i] = -1;
            } else {
                s.V[i] = 0;
            }
        }
        return s;
    }

    /**
     * Returns the sum of all elements in the vector.
     */
    public double sum() {
        double s = 0;
        for (int i = 0; i < size(); i++) {
            s += V[i];
        }
        return s;
    }

    /**
     * Returns the squared sum of all elements in the vector.
     */
    public double sum2() {
        double s2 = 0;
        for (int i = 0; i < size(); i++) {
            s2 += V[i] * V[i];
        }
        return s2;
    }

    /**
     * Returns the L1-norm of the vector
     */
    public double norm1() {
        double s = 0;
        for (int i = 0; i < size(); i++) {
            s += Math.abs(V[i]);
        }
        return s;
    }

    /**
     * Returns the L2-norm of the vector
     */
    public double norm2() {
        return Math.sqrt(sum2());
    }

    /**
     * Returns ||u-v||^2
     * 
     * @param v the second vector
     */
    public double sum2(DoubleVector v) {
        return minus(v).sum2();
    }

    /**
     * Returns a subvector.
     * 
     * @param i0 the index of the first element
     * @param i1 the index of the last element
     * @return v[i0:i1]
     */
    public DoubleVector subvector(int i0, int i1) {
        DoubleVector v = new DoubleVector(i1 - i0 + 1);
        v.set(0, i1 - i0, this, i0);
        return v;
    }

    /**
     * Returns a subvector.
     * 
     * @param index stores the indices of the needed elements
     * @return v[index]
     */
    public DoubleVector subvector(IntVector index) {
        DoubleVector v = new DoubleVector(index.size());
        for (int i = 0; i < index.size(); i++) {
            v.V[i] = V[index.V[i]];
        }
        return v;
    }

    /**
     * Returns a vector from the pivoting indices. Elements not indexed are set to
     * zero.
     * 
     * @param index  stores the pivoting indices
     * @param length the total number of the potential elements
     * @return the subvector
     */
    public DoubleVector unpivoting(IntVector index, int length) {
        if (index.size() > length) {
            throw new IllegalArgumentException("index.size() > length ");
        }
        DoubleVector u = new DoubleVector(length);
        for (int i = 0; i < index.size(); i++) {
            u.V[index.V[i]] = V[i];
        }
        return u;
    }

    /**
     * Adds a value to all the elements
     * 
     * @param x the value
     */
    public DoubleVector plus(double x) {
        return copy().plusEquals(x);
    }

    /**
     * Adds a value to all the elements in place
     * 
     * @param x the value
     */
    public DoubleVector plusEquals(double x) {
        for (int i = 0; i < size(); i++) {
            V[i] += x;
        }
        return this;
    }

    /**
     * Adds another vector element by element
     * 
     * @param v the second vector
     */
    public DoubleVector plus(DoubleVector v) {
        return copy().plusEquals(v);
    }

    /**
     * Adds another vector in place element by element
     * 
     * @param v the second vector
     */
    public DoubleVector plusEquals(DoubleVector v) {
        for (int i = 0; i < size(); i++) {
            V[i] += v.V[i];
        }
        return this;
    }

    /**
     * Subtracts a value
     * 
     * @param x the value
     */
    public DoubleVector minus(double x) {
        return plus(-x);
    }

    /**
     * Subtracts a value in place
     * 
     * @param x the value
     */
    public DoubleVector minusEquals(double x) {
        plusEquals(-x);
        return this;
    }

    /**
     * Subtracts another DoubleVector element by element
     * 
     * @param v the second DoubleVector
     */
    public DoubleVector minus(DoubleVector v) {
        return copy().minusEquals(v);
    }

    /**
     * Subtracts another DoubleVector element by element in place
     * 
     * @param v the second DoubleVector
     */
    public DoubleVector minusEquals(DoubleVector v) {
        for (int i = 0; i < size(); i++) {
            V[i] -= v.V[i];
        }
        return this;
    }

    /**
     * Multiplies a scalar
     * 
     * @param s scalar
     * @return s * v
     */
    public DoubleVector times(double s) {
        return copy().timesEquals(s);
    }

    /**
     * Multiply a vector by a scalar in place, u = s * u
     * 
     * @param s scalar
     * @return replace u by s * u
     */
    public DoubleVector timesEquals(double s) {
        for (int i = 0; i < size(); i++) {
            V[i] *= s;
        }
        return this;
    }

    /**
     * Multiplies another DoubleVector element by element
     * 
     * @param v the second DoubleVector
     */
    public DoubleVector times(DoubleVector v) {
        return copy().timesEquals(v);

    }

    /**
     * Multiplies another DoubleVector element by element in place
     * 
     * @param v the second DoubleVector
     */
    public DoubleVector timesEquals(DoubleVector v) {
        for (int i = 0; i < size(); i++) {
            V[i] *= v.V[i];
        }
        return this;
    }

    /**
     * Divided by another DoubleVector element by element
     * 
     * @param v the second DoubleVector
     */
    public DoubleVector dividedBy(DoubleVector v) {
        return copy().dividedByEquals(v);
    }

    /**
     * Divided by another DoubleVector element by element in place
     * 
     * @param v the second DoubleVector
     */
    public DoubleVector dividedByEquals(DoubleVector v) {
        for (int i = 0; i < size(); i++) {
            V[i] /= v.V[i];
        }
        return this;
    }

    /**
     * Checks if it is an empty vector
     */
    public boolean isEmpty() {
        if (size() == 0) {
            return true;
        }
        return false;
    }

    /**
     * Returns a vector that stores the cumulated values of the original vector
     */
    public DoubleVector cumulate() {
        return copy().cumulateInPlace();
    }

    /**
     * Cumulates the original vector in place
     */
    public DoubleVector cumulateInPlace() {
        for (int i = 1; i < size(); i++) {
            V[i] += V[i - 1];
        }
        return this;
    }

    /**
     * Returns the index of the maximum.
     * <p>
     * If multiple maximums exist, the index of the first is returned.
     */
    public int indexOfMax() {
        int index = 0;
        double ma = V[0];

        for (int i = 1; i < size(); i++) {
            if (ma < V[i]) {
                ma = V[i];
                index = i;
            }
        }
        return index;
    }

    /**
     * Returns true if vector not sorted
     */
    public boolean unsorted() {
        if (size() < 2) {
            return false;
        }
        for (int i = 1; i < size(); i++) {
            if (V[i - 1] > V[i]) {
                return true;
            }
        }
        return false;
    }

    /**
     * Combine two vectors together
     * 
     * @param v the second vector
     */
    public DoubleVector cat(DoubleVector v) {
        DoubleVector w = new DoubleVector(size() + v.size());
        w.set(0, size() - 1, this, 0);
        w.set(size(), size() + v.size() - 1, v, 0);
        return w;
    }

    /**
     * Swaps the values stored at i and j
     * 
     * @param i the index i
     * @param j the index j
     */
    public void swap(int i, int j) {
        if (i == j) {
            return;
        }
        double t = V[i];
        V[i] = V[j];
        V[j] = t;
    }

    /**
     * Returns the maximum value of all elements
     */
    public double max() {
        if (size() < 1) {
            throw new IllegalArgumentException("zero size");
        }
        double ma = V[0];
        if (size() < 2) {
            return ma;
        }
        for (int i = 1; i < size(); i++) {
            if (V[i] > ma) {
                ma = V[i];
            }
        }
        return ma;
    }

    /**
     * Applies a method to the vector
     * 
     * @param className the class name
     * @param method    the method
     */
    public DoubleVector map(String className, String method) {
        try {
            Class<?> c = Class.forName(className);
            Class<?>[] cs = new Class[1];
            cs[0] = Double.TYPE;
            Method m = c.getMethod(method, cs);

            DoubleVector w = new DoubleVector(size());
            Object[] obj = new Object[1];
            for (int i = 0; i < size(); i++) {
                obj[0] = new Double(V[i]);
                w.set(i, Double.parseDouble(m.invoke(null, obj).toString()));
            }
            return w;
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        return null;
    }

    /**
     * Returns the reverse vector
     */
    public DoubleVector rev() {
        int n = size();
        DoubleVector w = new DoubleVector(n);
        for (int i = 0; i < n; i++) {
            w.V[i] = V[n - i - 1];
        }
        return w;
    }

    /**
     * Returns a random vector of uniform distribution
     * 
     * @param n the size of the vector
     */
    public static DoubleVector random(int n) {
        DoubleVector v = new DoubleVector(n);
        for (int i = 0; i < n; i++) {
            v.V[i] = Math.random();
        }
        return v;
    }

    /**
     * Convert the DoubleVecor to a string
     */
    @Override
    public String toString() {
        return toString(5, false);
    }

    /**
     * Convert the DoubleVecor to a string
     * 
     * @param digits   the number of digits after decimal point
     * @param trailing true if trailing zeros are to be shown
     */
    public String toString(int digits, boolean trailing) {
        if (isEmpty()) {
            return "null vector";
        }

        StringBuffer text = new StringBuffer();
        FlexibleDecimalFormat nf = new FlexibleDecimalFormat(digits, trailing);
        nf.grouping(true);
        for (int i = 0; i < size(); i++) {
            nf.update(V[i]);
        }
        int count = 0;
        int width = 80;
        String number;
        for (int i = 0; i < size(); i++) {
            number = nf.format(V[i]);
            count += 1 + number.length();
            if (count > width - 1) {
                text.append('\n');
                count = 1 + number.length();
            }
            text.append(" " + number);
        }

        return text.toString();
    }

    public static void main(String args[]) {

        DoubleVector v = random(10);
        DoubleVector a = random(10);
        DoubleVector w = a;

        System.out.println(random(10).plus(v).plus(w));

    }
}
